﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using System.IO;
using System.Xml.Serialization;

namespace LGE
{
    public class Block
    {
        public static int nLayers = 3;
        public static int Width = 20;
        public static int Height = 24;
        public static int UndergroundHeight = 30;
        public static Dictionary<int, Action<Block, Point>> checkCodeActions;

        public Vector2 position;
        public String filePath = "ERROR 741";

        /// <summary>
        /// map[k][j][i]
        /// k = layers
        ///     - 0: background
        ///     - 1: mid-ground (objects collision)
        ///     - 2: foreground
        /// j = height
        /// i = width
        /// </summary>
        public int[][][] map;

        public static void Initialize(int width, int height)
        {
            checkCodeActions = new Dictionary<int, Action<Block, Point>>();
            Width = width;
            Height = height;
        }

        public Block(Vector2 pos, String filepath)
        {
            LoadBlock(filepath);
            this.position = pos;
        }

        public Block()
        {
            this.position = Vector2.Zero;
            map = new int[nLayers][][];

            for (int k = 0; k < nLayers; ++k)
            {
                map[k] = new int[Height][];
                for (int j = 0; j < Height; j++)
                {
                    map[k][j] = new int[Width];
                    for (int i = 0; i < Width; i++)
                        map[k][j][i] = 0;
                }
            }
        }

        public void Save(String filePath)
        {
            // Open the file, creating it if necessary
            FileStream stream = File.Open(filePath, FileMode.Create);
            try
            {
                // Convert the object to XML data and put it in the stream
                XmlSerializer serializer = new XmlSerializer(typeof(Block));
                serializer.Serialize(stream, this);
            }
            finally { stream.Close(); }
        }

        public static Block Load(String filePath)
        {
            Block data = new Block();
            // Open the file
            FileStream stream = File.Open(filePath, FileMode.OpenOrCreate, FileAccess.Read);
            try
            {
                // Read the data from the file
                XmlSerializer serializer = new XmlSerializer(typeof(Block));
                data = (Block)serializer.Deserialize(stream);
            }
            catch { }
            finally
            {
                stream.Close();
            }
            return data;
        }

        public void LoadBlock(String filePath)
        {
            Block temp = Load(filePath);
            filePath = filePath.Replace("Content\\", "").Replace("Blocks\\", "").Replace(".block", "");
            this.filePath = filePath;
            this.map = temp.map;
        }

        /* // antigo método de Load (via leitura de arquivo txt)
        public bool LoadBlock(String filepath)
        {
            StreamReader file = new StreamReader(filepath);
            try
            {
                map = new int[Height][];
                for (int j = 0; j < Height; j++)
                {
                    map[j] = new int[Width];
                    String[] line = file.ReadLine().Split(new char[] { ' ' });
                    for (int i = 0; i < Width; i++)
                        map[j][i] = int.Parse(line[i]);
                }
            }
            catch { return false; }
            finally { file.Close(); }
            return true;
        }*/

        public void Draw(SpriteBatch spriteBatch, GameTime gameTime, int layer)
        {
            Draw(spriteBatch, gameTime, layer, Vector2.Zero);
        }

        public void Draw(SpriteBatch spriteBatch, GameTime gameTime, int layer, Vector2 relativeObjectPosition)
        {
            for (int i = 0; i < Height; i++)
                for (int j = 0; j < Width; j++)
                    if (map[layer][i][j] != 0)
                        Tile.Draw(spriteBatch, gameTime, map[layer][i][j], position + new Vector2(j * Tile.Size, i * Tile.Size) - relativeObjectPosition);
        }
                
        public void DrawUnderground(SpriteBatch spriteBatch, Vector2 relativeObjectPosition)
        {
            for (int i = Height; i < Height + UndergroundHeight; i++)
                for (int j = 0; j < Width; j++)
                    spriteBatch.Draw(Tile.Textures[Tile.UndergroundCode], position + new Vector2(j * Tile.Size, i * Tile.Size) - relativeObjectPosition, Color.White);
        }
        
        public int GetCode(int layer, int j, int i)
        {
            if (j < 0 || j > Height - 1)
                return -1;
            if (i < 0 || i > Width - 1)
                return -1;
            return map[layer][j][i];
        }

        #region Métodos Auxiliares
        public Vector2 GetCentralPos(Vector2 p)
        {
            p -= position;
            return new Vector2(((int)p.X / Tile.Size) * Tile.Size + Tile.Size / 2, ((int)p.Y / Tile.Size) * Tile.Size + Tile.Size / 2);
        }

        public Point GetCoord(Vector2 p)
        {
            p -= position;
            if (p.X < 0) p.X -= Tile.Size;
            if (p.Y < 0) p.Y -= Tile.Size;
            return new Point((int)(p.X / Tile.Size), (int)(p.Y / Tile.Size));
        }

        public Vector2 GetPos(Point p)
        {
            return position + new Vector2(p.X * Tile.Size + Tile.Size / 2, p.Y * Tile.Size + Tile.Size / 2);
        }

        #endregion

        // Collision

        public float CollisionUp(EngineObject obj, bool checkCodes)
        {
            List<Point> list = GetCollidingTiles(obj);

            if (checkCodes) list.ForEach(p => CheckCode(p));

            list.RemoveAll(p => map[1][p.Y][p.X] == 0);

            float r = float.MaxValue;
            foreach (Point p in list)
                r = Math.Min(r, (GetPos(p).Y + Tile.Size / 2) - (obj.position.Y - obj.HitBox.Height / 2));
            return r == float.MaxValue ? 0 : r;
        }

        public float CollisionDown(EngineObject obj, bool checkCodes)
        {
            List<Point> list = GetCollidingTiles(obj);

            if (checkCodes) list.ForEach(p => CheckCode(p));

            list.RemoveAll(p => map[1][p.Y][p.X] == 0);

            float r = float.MaxValue;
            foreach (Point p in list)
                r = Math.Min(r, (obj.position.Y + obj.HitBox.Height / 2) - (GetPos(p).Y - Tile.Size / 2));
            return r == float.MaxValue ? 0 : -r;
        }

        public float CollisionLeft(EngineObject obj, bool checkCodes)
        {
            List<Point> list = GetCollidingTiles(obj);

            if (checkCodes) list.ForEach(p => CheckCode(p));

            list.RemoveAll(p => map[1][p.Y][p.X] == 0);

            float r = float.MaxValue;
            foreach (Point p in list)
                r = Math.Min(r, (GetPos(p).X + Tile.Size / 2) - (obj.position.X - obj.HitBox.Width / 2));
            return r == float.MaxValue ? 0 : r;
        }

        public float CollisionRight(EngineObject obj, bool checkCodes)
        {
            List<Point> list = GetCollidingTiles(obj);

            if (checkCodes) list.ForEach(p => CheckCode(p));

            list.RemoveAll(p => map[1][p.Y][p.X] == 0);

            float r = float.MaxValue;
            foreach (Point p in list)
                r = Math.Min(r, (obj.position.X + obj.HitBox.Width / 2) - (GetPos(p).X - Tile.Size / 2));
            return r == float.MaxValue ? 0 : -r;
        }

        public List<Point> GetCollidingTiles(EngineObject obj)
        {
            List<Point> list = new List<Point>();

            Point tempCoord = Point.Zero;
            Point initCoord = GetCoord(new Vector2(obj.HitBox.Left, obj.HitBox.Top));
            Point endCoord = GetCoord(new Vector2(obj.HitBox.Right, obj.HitBox.Bottom));

            for (tempCoord.X = initCoord.X; tempCoord.X <= endCoord.X; tempCoord.X++)
                for (tempCoord.Y = initCoord.Y; tempCoord.Y <= endCoord.Y; tempCoord.Y++)
                    AddPointInList(tempCoord, ref list);

            return list;
        }

        public void AddPointInList(Point p, ref List<Point> list)
        {
            if (p.X >= 0 && p.Y >= 0 && p.X < Block.Width && p.Y < Block.Height && !list.Contains(p)) list.Add(p);
        }

        private void CheckCode(Point p)
        {
            for (int i = 0; i < nLayers; ++i)
                if (checkCodeActions.ContainsKey(map[i][p.Y][p.X]))
                    checkCodeActions[map[i][p.Y][p.X]](this, p);

        }
    }
}